home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
BCI NET
/
BCI NET Dec 94.iso
/
archives
/
programming
/
source
/
fbm12s.lha
/
fbquant.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-07-18
|
35KB
|
1,231 lines
/****************************************************************
* fbquant.c: FBM Release 1.2 07-Apr-93 Michael Mauldin
*
* Copyright (C) 1989-1993 by Michael Mauldin. Permission is granted
* to use this file in whole or in part for any purpose, educational,
* recreational or commercial, provided that this copyright notice
* is retained unchanged. This software is available to all free of
* charge by anonymous FTP and in the UUNET archives.
*
* fbquant: Convert an RGB color image to mapped color format (color
* quantization step). Floyd-Steinberg dithering is used
* to reduce color banding. The quantization used is a
* modification of Heckbert's median cut.
*
* USAGE
* % fbquant [ -c<numcolors> ] [ -r<bits> ] [ -m<mapimage> ]
* [ -n ] [ -<type> ]
* [ -i <num> <red> <grn> <blu> ]
* [ -s <num> <red> <grn> <blu> ]
* [ -C <color-config> ]
* < rgb > mapped"
*
* EDITLOG
* LastEditDate = Thu Oct 11 22:48:22 1990 - Michael Mauldin
* LastFileName = /usr2/mlm/src/misc/fbm/fbquant.c
*
* HISTORY
* 07-Apr-93 Michael Mauldin (mlm) at Carnegie-Mellon University
* Added -J switch
*
* 25-Jun-90 Michael Mauldin (mlm@cs.cmu.edu) Carnegie Mellon
* Package for Release 1.0
*
* 04-Sep-89 Michael Mauldin (mlm) at Carnegie Mellon University
* Add option for low-res colors, default assumes each RGB value
* can vary from 0 to 255. The default is 8 bits for more than 32
* color images, and 4 bits for images with 32 or fewer colors.
* For example, the Amiga has 4bit DACs and can only display
* 4^3 == 4096 colors.
*
* 03-May-89 Michael Mauldin (mlm) at Carnegie Mellon University
* Clear FBM pointers before allocate
*
* 07-Apr-89 Michael Mauldin (mlm) at Carnegie Mellon University
* Add -m<image> flag for using colormap from another image.
*
* 07-Mar-89 Michael Mauldin (mlm) at Carnegie Mellon University
* Beta release (version 0.93) mlm@cs.cmu.edu
*
* 26-Feb-89 Michael Mauldin (mlm) at Carnegie Mellon University
* Changes for small color maps. Fixed bug with unsigned
* arithmetic that ruined dithering for images with small
* colormaps. Added error limiting in the Floyd-Steinberg
* code to prevent color "shadowing" that occurs with small
* numbers of colors. Also change to use colors 0..n-1 instead
* of reserving colors 0 and n-1 for Sun foreground/background
* colors, unless output type is SUN.
*
* 11-Nov-88 Michael Mauldin (mlm) at Carnegie Mellon University
* Created.
*
* References: Uses a variant of Heckbert's adaptive partitioning
* algorithm. See Computer Graphics, v16n3 July 1982
*
****************************************************************/
# include <stdio.h>
# include <ctype.h>
# include <math.h>
# include "fbm.h"
int cmp_red(), cmp_grn(), cmp_blu(), cmp_cmap(), cmp_int(), cmp_hue();
# define RD 0
# define GR 1
# define BL 2
# define REDMASK 0076000
# define REDSHFT 10
# define GRNMASK 0001740
# define GRNSHFT 5
# define BLUMASK 0000037
# define BLUSHFT 0
# define CUBITS 5
# define CUBIGN (8-CUBITS)
# define CUBSID 32
# define CUBSIZ 32768
# define MAXSHRT 32767
# define MAXERR 32
# define GETR(X) (((X) & REDMASK) >> REDSHFT)
# define GETG(X) (((X) & GRNMASK) >> GRNSHFT)
# define GETB(X) ((X) & BLUMASK)
# define CLRINDEX(R,G,B) \
(((R) << REDSHFT) & REDMASK | \
((G) << GRNSHFT) & GRNMASK | \
((B) & BLUMASK))
# define CLRINDEX8(R,G,B) \
(((R) << (REDSHFT-CUBIGN)) & REDMASK | \
((G) << (GRNSHFT-CUBIGN)) & GRNMASK | \
((B) >> (CUBIGN)) & BLUMASK)
# define GETR8(X) (((X) & REDMASK) >> (REDSHFT-CUBIGN))
# define GETG8(X) (((X) & GRNMASK) >> (GRNSHFT-CUBIGN))
# define GETB8(X) (((X) & BLUMASK) << CUBIGN)
# define abs(X) (((X)<0)?-(X):(X))
typedef struct cstruct {
unsigned char rd, gr, bl, indx;
short flags;
} COLOR;
# define FIXED 1 /* This color is set by the user */
# define UNUSABLE 2 /* This color may not be used when dithering */
COLOR *cmap = NULL;
COLOR fmap[256];
int freecolors=0, fixedcolors=0, unusable=0, usable=0;
typedef struct pix_struct {
short cnt;
short color;
} PIXEL;
int debug=0, verbose=0, colors=256, showcolor=0, dither=1, hue=0;
int resbits = -1, resmask = 0xff;
double flesh = 1.0, atof();
int fr = 250, fg = 150, fb = 80;
int outtype = DEF_8BIT; /* Output format desired */
/****************************************************************
* main
****************************************************************/
# define USAGE \
"Usage: fbquant [ -c<numcolors> ] [ -r<bits> ] [ -m<mapimage> ]\n\
[ -f<flesh> ] [ -n ] [ -<type> ] < rgb > mapped"
#ifndef lint
static char *fbmid =
"$FBM fbquant.c <1.2> 07-Apr-93 (C) 1989-1993 by Michael Mauldin, source \
code available free from MLM@CS.CMU.EDU and from UUNET archives$";
#endif
main (argc, argv)
char *argv[];
{ FBM input, output, mapimage; /* Images */
int hist[CUBSIZ]; /* Color cube 32x32x32 for histogram */
char *mapfile = NULL; /* Name of file to copy map from */
register int c;
int aindx, ard, agr, abl; /* Get 'i' arguments from sscanf */
int i, bad=0; /* Error checking temp */
/* Clear pointers */
input.bm = input.cm = (unsigned char *) NULL;
output.bm = output.cm = (unsigned char *) NULL;
mapimage.bm = mapimage.cm = (unsigned char *) NULL;
/* Get the options */
while (--argc > 0 && (*++argv)[0] == '-')
{ while (*++(*argv))
{ switch (**argv)
{ case 'c': colors = atoi (*argv+1); SKIPARG; break;
case 'f': flesh = atof (*argv+1); SKIPARG; break;
case 'r': resbits = atoi (*argv+1); SKIPARG; break;
case 'm': mapfile = *argv+1; SKIPARG; break;
case 'n': dither = 0; break;
case 'd': debug++; break;
case 'D': showcolor++; break;
case 'v': verbose++; break;
case 'h': if (isdigit (argv[0][1]))
{ hue = atoi (*argv+1); SKIPARG; }
else
{ hue = 32; }
break;
case 'C': load_config (*argv+1); SKIPARG; break;
case 'A': outtype = FMT_ATK; break;
case 'B': outtype = FMT_FACE; break;
case 'F': outtype = FMT_FBM; break;
case 'G': outtype = FMT_GIF; break;
case 'I': outtype = FMT_IFF; break;
case 'J': outtype = FMT_JPEG; break;
case 'L': outtype = FMT_LEAF; break;
case 'M': outtype = FMT_MCP; break;
case 'P': outtype = FMT_PBM; break;
case 'R': outtype = FMT_RLE; break;
case 'S': outtype = FMT_SUN; break;
case 'T': outtype = FMT_TIFF; break;
case 'X': outtype = FMT_X11; break;
case 'Z': outtype = FMT_PCX; break;
case 'i': unusable++;
/* fall through */
case 's': if (sscanf (*argv+1, "%d,%d,%d,%d",
&aindx, &ard, &agr, &abl))
{ fmap[fixedcolors].indx = aindx;
fmap[fixedcolors].rd = ard;
fmap[fixedcolors].gr = agr;
fmap[fixedcolors].bl = abl;
fmap[fixedcolors].flags =
FIXED | (**argv == 'i' ? UNUSABLE : 0);
fixedcolors++;
SKIPARG;
}
break;
default: fprintf (stderr, "%s\n", USAGE);
exit (1);
}
}
}
freecolors = colors - fixedcolors;
usable = colors - unusable;
/* Check fixed colors for being out of range */
for (i=0, bad=0; i<fixedcolors; i++)
{ if (fmap[i].indx >= colors)
{ fprintf (stderr, "Error: fixed color %d is out of range [0..%d]\n",
fmap[i].indx, colors-1);
bad++;
}
}
if (bad) exit (1);
/* Check arguments */
if (colors > 256 || freecolors < 4 || colors < 4)
{ fprintf (stderr, "fbquant can only handle 4..256 colors\n");
exit (1);
}
/* Choose default resbits: if using few colors, assume low-res */
if (resbits < 0)
{ if (colors <= 32) resbits = 4;
else resbits = 8;
}
if (resbits > 8 || resbits < 1)
{ fprintf (stderr, "color resolution (-r) must be 1..8 bits\n");
exit (1);
}
resmask = (0xff - (0xff >> resbits));
/* Open file if name given as argument */
if (! read_bitmap (&input, (argc > 0) ? argv[0] : (char *) NULL))
{ exit (1); }
/* Read colormap from map image (if specified) */
if (mapfile != NULL)
{ if (!read_bitmap (&mapimage, mapfile))
{ perror (mapfile); exit (1); }
if (mapimage.hdr.clrlen == 0)
{ fprintf (stderr, "fbquant: %s: no map\n", mapfile); exit (1); }
/* Dont care about the map image, just its colormap */
free (mapimage.bm); mapimage.bm = NULL;
colors = mapimage.hdr.clrlen / 3;
freecolors = colors - fixedcolors;
usable = colors - unusable;
cmap = (COLOR *) malloc ((unsigned) colors * sizeof (COLOR));
for (c=0; c<colors; c++)
{ cmap[c].rd = mapimage.cm[c];
cmap[c].gr = mapimage.cm[c+colors];
cmap[c].bl = mapimage.cm[c+colors*2];
cmap[c].indx = c;
cmap[c].flags = FIXED;
}
fprintf (stderr, "Quantizing \"%s\" [%dx%d] with %d colors from %s\n",
input.hdr.title, input.hdr.cols, input.hdr.rows, colors, mapfile);
}
else
{ fprintf (stderr,
"Quantizing \"%s\" [%dx%d] with %d colors, %d bits/clr\n",
input.hdr.title, input.hdr.cols, input.hdr.rows, colors, resbits);
}
if (input.hdr.planes != 3 || input.hdr.bits != 8)
{ fprintf (stderr, "fbquant %s, input has %d planes and %d bits\n",
"can only handle 24bit RGB inputs",
input.hdr.planes, input.hdr.bits);
exit (1);
}
/* Now build header for output bit map */
output.hdr = input.hdr;
output.hdr.planes = 1;
output.hdr.clrlen = 3 * colors;
/* Allocate space for output */
alloc_fbm (&output);
/* Build colormap by sampling and mcut if not specified */
if (mapfile == NULL)
{
cmap = (COLOR *) malloc ((unsigned) colors * sizeof (COLOR));
sample_image (&input, hist);
build_colormap (hist, cmap, colors, fmap, fixedcolors);
}
/* Use Floyd-Steinberg error diffusion to quantize using the new cmap */
clr_quantize (&input, &output, cmap, colors);
/* Write out the result */
if (write_bitmap (&output, stdout, outtype))
{ exit (0); }
exit (1);
}
/****************************************************************
* load_config: Read a series of fixed/ignore color settings from a file
****************************************************************/
load_config (filenm)
char *filenm;
{ FILE *infile = NULL;
char buf[BUFSIZ];
int aindx, ard, agr, abl; /* Get 'i' arguments from sscanf */
if ((infile = fopen (filenm, "r")) == NULL)
{ perror (filenm); exit (1); }
while (fgets (buf, BUFSIZ, infile))
{
if (index ("#;:/\n", *buf)) return;
if (*buf != 'i' && *buf != 's')
{ fprintf (stderr, "%s\nInput was: %s",
"Ignoring bad line in %s (must start with 'i' or 's')", buf);
}
if (sscanf (buf+1, "%d,%d,%d,%d", &aindx, &ard, &agr, &abl))
{ fmap[fixedcolors].indx = aindx;
fmap[fixedcolors].rd = ard;
fmap[fixedcolors].gr = agr;
fmap[fixedcolors].bl = abl;
fmap[fixedcolors].flags = FIXED | (*buf == 'i' ? UNUSABLE : 0);
fixedcolors++;
if (*buf == 'i') unusable++;
}
}
}
/****************************************************************
* sample_image:
****************************************************************/
sample_image (image, hist)
FBM *image;
int *hist;
{ register int i, j;
register unsigned char *rp, *gp, *bp;
int width = image->hdr.cols, height = image->hdr.rows;
int rowlen = image->hdr.rowlen, plnlen = image->hdr.plnlen;
int used=0;
/* Clear the histogram */
for (i=0; i<CUBSIZ; i++) hist[i] = 0;
/* Now count colors */
for (j=0; j<height; j++)
{ rp = &(image->bm[j*rowlen]);
gp = rp+plnlen;
bp = gp+plnlen;
/* If resolution is greater than cube size, just count */
if (resbits >= CUBITS)
{ for (i=0; i<width; i++)
{ if (++hist[ CLRINDEX8 (*rp++, *gp++, *bp++) ] == 1) used++; }
}
/* If resolution is less than cube size, ignore extra bits */
else
{ for (i=0; i<width; i++)
{ if (++hist[ CLRINDEX8 (*rp++ & resmask,
*gp++ & resmask,
*bp++ & resmask) ] == 1)
{ used++; }
}
}
}
/* Bonus for flesh color */
if (flesh < 0.99 || flesh > 1.01)
{ register int diff = 0, new;
int maxdiff = 0;
int changes = 0;
double bonus;
maxdiff += (fr > 127) ? fr : 256 - fr;
maxdiff += (fg > 127) ? fr : 256 - fr;
maxdiff += (fb > 127) ? fr : 256 - fr;
for (i=0; i<CUBSIZ; i++)
{ if (hist[i] > 0)
{ j = GETR8(i) - fr; j = abs (j); diff = j;
j = GETG8(i) - fr; j = abs (j); diff += j;
j = GETB8(i) - fr; j = abs (j); diff += j;
if (diff < maxdiff)
{ bonus = 1.0 + flesh * ((double) (maxdiff - diff) / maxdiff);
new = ((double) hist[i] * bonus + 0.5);
if (new < 0) new = 0;
if (new != hist[i])
{ hist[i] = new; changes++; }
}
}
}
fprintf (stderr, "Flesh bonus %1.4lf, max diff %d, changed %d boxes\n",
flesh, maxdiff, changes);
}
if (debug)
{ fprintf (stderr, "Done counting, used %d colors for %d pixels\n",
used, width*height);
}
}
/****************************************************************
* build_colormap:
****************************************************************/
# define SWAP(A,B) (t=A,A=B,B=t)
build_colormap (hist, cmap, colors)
int *hist, colors;
COLOR *cmap;
{ register int i, k;
PIXEL box[CUBSIZ];
register PIXEL *b;
int arrange[256];
int used=0, t, freecolors = colors-fixedcolors;
int usable = colors - unusable;;
/* Build the first box, encompassing all pixels */
for (b=box, i=0; i<CUBSIZ; i++)
{ b->color = i;
k = hist[i];
b->cnt = (k > MAXSHRT) ? MAXSHRT : k;
b++;
}
/* Move all non-zero count pixels to the front of the list */
for (i=0, used=0; i<CUBSIZ; i++)
{ if (box[i].cnt > 0) box[used++] = box[i]; }
/*-------- Special case if we didnt need all colors --------*/
if (used <= freecolors)
{
/* Copy the colors actually found */
for (i=0; i<used; i++)
{ cmap[i].rd = GETR8 (box[i].color);
cmap[i].gr = GETG8 (box[i].color);
cmap[i].bl = GETB8 (box[i].color);
cmap[i].indx = i;
cmap[i].flags = 0;
}
/* Set the rest to WHITE */
for (; i<colors; i++)
{ cmap[i].rd = 255;
cmap[i].gr = 255;
cmap[i].bl = 255;
cmap[i].indx = i;
cmap[i].flags = UNUSABLE;
}
}
else /* Build sets all colors */
{ split_box (box, used, 0, colors, cmap); }
/*----------------------------------------------------------------
* Now arrange the colors in the desired order. Sun convention is that
* color 0 is white and color n-1 is black, so we sort by increasing
* intensity (why not?) and then swap the lightest and darkest colors
*/
/* Now sort 0..n-1 according to increasing intensity */
qsort (cmap, colors, sizeof (* cmap), hue ? cmp_hue : cmp_int);
/* Make first color lightest and last color darkest */
if (outtype == FMT_SUN && fixedcolors == 0)
{ SWAP (cmap[0].rd, cmap[colors-1].rd);
SWAP (cmap[0].gr, cmap[colors-1].gr);
SWAP (cmap[0].bl, cmap[colors-1].bl);
}
/* Now compute new arrangement of free colors around the fixed colors */
if (fixedcolors)
{
/* Make arrange array: 1=regular color, 0=fixed color */
for (i=0; i<colors; i++) arrange[i] = 1;
for (i=0; i<fixedcolors; i++) arrange[fmap[i].indx] = 0;
/* Arrange[i] is new location of color after inserting fixed colors */
for (i=0, k=0; i<colors; i++)
{ if (arrange[i]) arrange[i] = k++; }
/* Now rearrange colors around fixed colors */
for (i=colors-1; i>=0; i--)
{ cmap[i] = cmap[arrange[i]];
cmap[i].indx = i;
}
/* Now insert the fixed colors */
for (i=0; i<fixedcolors; i++)
{ cmap[fmap[i].indx] = fmap[i];
cmap[fmap[i].indx].indx = -1;
}
}
else
{ /* Set the output indices */
for (i=0; i<colors; i++) { cmap[i].indx = i; }
}
}
/****************************************************************
* split_box: Basic recursive part of Heckberts adaptive partitioning
* algorithm.
****************************************************************/
split_box (box, boxlen, clr, numclr, cmap)
PIXEL *box;
int boxlen, clr, numclr;
COLOR *cmap;
{ int maxv[3], minv[3], numv[3];
int pcnt[3][CUBSID];
int sbox, snum, split, half, maxdif, dif;
register PIXEL *top, *bot;
int topw, botw;
int red, grn, blu;
register int i, c;
/* If numclr exceeds boxlen, we are in trouble */
if (numclr > boxlen)
{ fprintf (stderr, "boxlen %d is less numclr %d, panic!\n than",
boxlen, numclr);
fflush (stderr);
abort ();
}
/* Base case: only one color, assign the average for this cell */
if (numclr <= 1)
{ red = box_avg_red (box, boxlen);
grn = box_avg_grn (box, boxlen);
blu = box_avg_blu (box, boxlen);
/* Map x to x+4, because the histogram maps values to multiples of 8 */
cmap[clr].rd = red + 4;
cmap[clr].gr = grn + 4;
cmap[clr].bl = blu + 4;
cmap[clr].indx = clr;
cmap[clr].flags = 0;
/* Mask off unused bits for color resolution */
cmap[clr].rd &= resmask;
cmap[clr].gr &= resmask;
cmap[clr].bl &= resmask;
/* Should repeat highlevel bits in lower bits to even out values */
if (debug && verbose)
{ fprintf (stderr, "\t\tassigning color %d <%d,%d,%d> (%d)\n",
clr, cmap[clr].rd, cmap[clr].gr, cmap[clr].bl,
box_weight (box, boxlen));
}
return;
}
/* Gather statistics about the boxes contents */
minv[RD] = minv[GR] = minv[BL] = CUBSID;
maxv[RD] = maxv[GR] = maxv[BL] = 0;
numv[RD] = numv[GR] = numv[BL] = 0;
for (i=0; i<CUBSID; i++) { pcnt[RD][i] = pcnt[GR][i] = pcnt[BL][i] = 0; }
for (i=0; i<boxlen; i++)
{ c = box[i].color;
red = GETR(c); grn = GETG(c); blu = GETB(c);
if (red < minv[RD]) minv[RD] = red;
if (red > maxv[RD]) maxv[RD] = red;
if (grn < minv[GR]) minv[GR] = grn;
if (grn > maxv[GR]) maxv[GR] = grn;
if (blu < minv[BL]) minv[BL] = blu;
if (blu > maxv[BL]) maxv[BL] = blu;
if (++pcnt[RD][red] == 1) numv[RD]++;
if (++pcnt[GR][grn] == 1) numv[GR]++;
if (++pcnt[BL][blu] == 1) numv[BL]++;
}
/* Special case, boxlen = numclr, just assign each box one color */
if (boxlen == numclr)
{ for (i=0; i<boxlen; i++)
{ split_box (box+i, 1, clr+i, 1, cmap); }
return;
}
/*
* Pick a dimension to split. Currently splits longest dimension
* in RGB space. Heckbert and other experts suggest splitting
* dimension with greatest variance may work better.
*/
split = -1; maxdif = -1;
if ((dif = (maxv[RD] - minv[RD])) > maxdif) { maxdif = dif; split = RD; }
if ((dif = (maxv[GR] - minv[GR])) > maxdif) { maxdif = dif; split = GR; }
if ((dif = (maxv[BL] - minv[BL])) > maxdif) { maxdif = dif; split = BL; }
/* Sort along the chosen dimension */
switch (split)
{ case RD: qsort (box, boxlen, sizeof (*box), cmp_red); break;
case GR: qsort (box, boxlen, sizeof (*box), cmp_grn); break;
case BL: qsort (box, boxlen, sizeof (*box), cmp_blu); break;
default: fprintf (stderr, "panic in split_box, split %d\n", split);
fflush (stderr); fflush (stdout); abort ();
}
/*
* Split at the median, but make sure there are at least numclr/2
* different colors on each side of the split, to avoid wasting
* colors.
*
* Note: need to keep in mind that when the box is large, dont split
* too close to one edge.
*/
half = numclr / 2;
top = box; bot = box + (boxlen-1);
topw = top->cnt; botw = bot->cnt;
/* Set top and bot to point to min/max feasible splits */
while ((top-box)+1 < half) { top++; topw += top->cnt; }
while ((boxlen-(bot-box)) < half) { bot--; botw += bot->cnt; }
/* Move top and bottom towards each other 1/8 of remaining distance */
c = (bot-top) / 8;
for (i=0; i<c; i++) { top++; topw += top->cnt; }
for (i=0; i<c; i++) { bot--; botw += bot->cnt; }
/* Now search for median */
while (top < bot)
{ if (topw < botw) { top++; topw += top->cnt; }
else { bot--; botw += bot->cnt; }
}
/* Decide which half gets the midpoint */
if (topw > botw) /* Median wants to go with top */
{ sbox = (top-box) + 1;
snum = numclr - half;
}
else /* Median wants to go with bottom */
{ sbox = (top - box);
snum = half;
}
/* Handle boundary conditions with number of colors vs box size */
if (sbox == 0) sbox++;
else if (sbox == boxlen) sbox--;
while (snum > sbox) snum--;
while (numclr-snum > boxlen-sbox) snum++;
# ifndef OPTIMIZE
/* Check for boundary condition errors */
if (snum <= 0 || snum >= numclr)
{ fprintf (stderr, "panic, using zero colors for box\n");
fflush (stderr);
abort ();
}
if (boxlen-sbox < numclr-snum)
{ fprintf (stderr, "panic, about to used %d boxes for %d colors\n",
boxlen-sbox, numclr-snum);
fflush (stderr);
abort ();
}
if (sbox < snum)
{ fprintf (stderr, "panic, about to used %d boxes for %d colors\n",
sbox, snum);
fflush (stderr);
abort ();
}
# endif
if (debug && verbose)
{ int count = numclr, depth = 8;
while (count > 0) { depth--; count /= 2; }
for (i=0; i<depth; i++) fprintf (stderr, " ");
fprintf (stderr, "box [%d..%d|%d] r(%d,%d,%d) g(%d,%d,%d) b(%d,%d,%d) =>",
0, boxlen-1, numclr,
minv[RD], maxv[RD], numv[RD],
minv[GR], maxv[GR], numv[GR],
minv[BL], maxv[BL], numv[BL]);
fprintf (stderr, " %c [%d..%d|%d] [%d..%d|%d]\n",
"RGB"[split], 0, sbox-1, snum, sbox, boxlen-1, numclr-snum);
}
/* Now recurse and split each sub-box */
split_box (box, sbox, clr, snum, cmap);
split_box (box+sbox, boxlen - sbox, clr+snum, numclr-snum, cmap);
}
/****************************************************************
* box_weight: Determine the total count of a box
****************************************************************/
box_weight (box, boxlen)
register PIXEL *box;
register int boxlen;
{ register int sum = 0, i;
for (i=0; i<boxlen; i++)
{ sum += box[i].cnt; }
return (sum);
}
/****************************************************************
* box_avg_red: Determine the average red value [0..255] of a box
****************************************************************/
box_avg_red (box, boxlen)
register PIXEL *box;
register int boxlen;
{ register int sum = 0, n = 0, i;
for (i=0; i<boxlen; i++)
{ sum += GETR8(box[i].color); n++; }
return (sum / n);
}
/****************************************************************
* box_avg_grn: Determine the average grn value [0..255] of a box
****************************************************************/
box_avg_grn (box, boxlen)
register PIXEL *box;
register int boxlen;
{ register int sum = 0, n = 0, i;
for (i=0; i<boxlen; i++)
{ sum += GETG8(box[i].color); n++; }
return (sum / n);
}
/****************************************************************
* box_avg_blu: Determine the average blu value [0..255] of a box
****************************************************************/
box_avg_blu (box, boxlen)
register PIXEL *box;
register int boxlen;
{ register int sum = 0, n = 0, i;
for (i=0; i<boxlen; i++)
{ sum += GETB8(box[i].color); n++; }
return (sum / n);
}
/****************************************************************
* sort by increasing red ( then green and blue )
****************************************************************/
cmp_red (a, b)
PIXEL *a, *b;
{ register r;
if (r = GETR(a->color) - GETR(b->color))
{ return (r); }
if (r = GETG(a->color) - GETG(b->color))
{ return (r); }
if (r = GETB(a->color) - GETB(b->color))
{ return (r); }
return (0);
}
/****************************************************************
* sort by increasing green ( then blue and red )
****************************************************************/
cmp_grn (a, b)
PIXEL *a, *b;
{ register r;
if (r = GETG(a->color) - GETG(b->color))
{ return (r); }
if (r = GETB(a->color) - GETB(b->color))
{ return (r); }
if (r = GETR(a->color) - GETR(b->color))
{ return (r); }
return (0);
}
/****************************************************************
* sort by increasing blue ( then red and green )
****************************************************************/
cmp_blu (a, b)
PIXEL *a, *b;
{ register r;
if (r = GETB(a->color) - GETB(b->color))
{ return (r); }
if (r = GETR(a->color) - GETR(b->color))
{ return (r); }
if (r = GETG(a->color) - GETG(b->color))
{ return (r); }
return (0);
}
/****************************************************************
* clr_quantize: Do Floyd Steinberg quantizing on the image
****************************************************************/
clr_quantize (input, output, cmap, colors, fmap, fixedcolors)
FBM *input, *output;
COLOR *cmap, *fmap;
int colors, fixedcolors;
{ int **cerr, **lerr, **terr;
int width = input->hdr.cols, height = input->hdr.rows;
int rowlen = input->hdr.rowlen, plnlen = input->hdr.plnlen;
register int i, j;
register int rd, gr, bl;
int rderr, grerr, blerr, clr;
unsigned char *rp, *gp, *bp, *obm;
/*-------- Copy colormap into output bitmap --------*/
for (i=0; i<colors; i++)
{ output->cm[i] = cmap[i].rd;
output->cm[i+colors] = cmap[i].gr;
output->cm[i+colors+colors] = cmap[i].bl;
}
/*
* Precompute necessary nearest neighbor query info. Note that we wait
* until after copying the colormap, since init reorders it
*/
init_nearest (cmap, colors);
/*-------- Do halftoning --------*/
cerr = (int **) malloc (3 * sizeof (int *));
lerr = (int **) malloc (3 * sizeof (int *));
cerr[RD] = (int *) malloc (width * sizeof (int));
cerr[GR] = (int *) malloc (width * sizeof (int));
cerr[BL] = (int *) malloc (width * sizeof (int));
lerr[RD] = (int *) malloc (width * sizeof (int));
lerr[GR] = (int *) malloc (width * sizeof (int));
lerr[BL] = (int *) malloc (width * sizeof (int));
/*-------- Just use the nearest color everywhere --------*/
if (!dither)
{
for (j=0; j<height; j++)
{ rp = &(input->bm[j*rowlen]);
gp = rp+plnlen;
bp = gp+plnlen;
obm = &(output->bm[j*rowlen]);
for (i=0; i<width; i++)
{ rd = *rp++; gr = *gp++; bl = *bp++;
obm[i] = cmap[nearest (rd, gr, bl, cmap, usable)].indx;
}
}
return;
}
/*------ Just use the nearest color around the left, right, and top ------*/
/* Top border */
rp = input->bm; gp = rp+plnlen; bp = gp+plnlen;
obm = output->bm;
for (i=0; i<width; i++)
{ obm[i] = cmap[nearest (rp[i], gp[i], bp[i], cmap, usable)].indx; }
/* Left border */
rp = input->bm; gp = rp+plnlen; bp = gp+plnlen;
obm = output->bm;
for (j=0; j<height; j++)
{ obm[j*rowlen] = cmap[nearest (rp[j*rowlen], gp[j*rowlen],
bp[j*rowlen], cmap, usable)].indx; }
/* Right border */
rp = &(input->bm[width-1]); gp = rp + plnlen; bp = gp + plnlen;
obm = &(output->bm[width-1]);
for (j=0; j<height; j++)
{ obm[j*rowlen] = cmap[nearest (rp[j*rowlen], gp[j*rowlen],
bp[j*rowlen], cmap, usable)].indx; }
/*-------- Clear error vectors --------*/
for (i=0; i<width; i++)
{ cerr[RD][i] = cerr[GR][i] = cerr[BL][i] = 0;
lerr[RD][i] = lerr[GR][i] = lerr[BL][i] = 0;
}
/*-------- Major loop for Floyd-Steinberg --------*/
for (j=1; j<height; j++)
{ rp = &(input->bm[j*rowlen+1]);
gp = rp+plnlen;
bp = gp+plnlen;
obm = &(output->bm[j*rowlen+1]);
for (i=1; i<width-1; i++)
{ rd = *rp++; gr = *gp++; bl = *bp++;
/* Sum up errors using Floyd-Steinberg weights */
rderr= cerr[RD][i-1] + 7*lerr[RD][i-1] + 5*lerr[RD][i] + 3*lerr[RD][i+1];
grerr= cerr[GR][i-1] + 7*lerr[GR][i-1] + 5*lerr[GR][i] + 3*lerr[GR][i+1];
blerr= cerr[BL][i-1] + 7*lerr[BL][i-1] + 5*lerr[BL][i] + 3*lerr[BL][i+1];
rderr >>= 4; /* Divide by 16 */
grerr >>= 4; /* Divide by 16 */
blerr >>= 4; /* Divide by 16 */
/* Choose nearest color to adjusted RGB value */
rd += rderr; gr += grerr; bl += blerr;
clr = nearest (rd, gr, bl, cmap, usable);
*obm++ = cmap[clr].indx;
/* Compute accumulated error for this pixel */
rd -= (int) cmap[clr].rd;
gr -= (int) cmap[clr].gr;
bl -= (int) cmap[clr].bl;
/* Limit error (avoids color shadows) to 75% if over MAXERR */
if (rd < -MAXERR || rd > MAXERR) rd = (rd * 3) >> 2;
if (gr < -MAXERR || gr > MAXERR) gr = (gr * 3) >> 2;
if (bl < -MAXERR || bl > MAXERR) bl = (bl * 3) >> 2;
/* Store errors in error vectors */
cerr[RD][i] = rd;
cerr[GR][i] = gr;
cerr[BL][i] = bl;
}
/* Swap error vectors */
terr = lerr; lerr = cerr; cerr = terr;
}
}
/****************************************************************
* nearest: Choose nearest color
****************************************************************/
short cache[CUBSIZ];
init_nearest (cmap, colors)
COLOR *cmap;
int colors;
{ register int i;
if (debug || showcolor)
{ fprintf (stderr, "\nFinal colormap (%d colors, %d fixed, %d usable):\n\n",
colors, fixedcolors, usable);
for (i=0; i<colors; i++)
{ fprintf (stderr, "%3d: <%3d,%3d,%3d> [%d]%s%s\n",
i, cmap[i].rd, cmap[i].gr, cmap[i].bl, cmap[i].indx,
(cmap[i].flags & FIXED) ? " fixed" : "",
(cmap[i].flags & UNUSABLE) ? " unusuable" : "");
}
}
/* Initialize the cache */
for (i=0; i<CUBSIZ; i++) { cache[i] = -1; }
/* Sort the colormap by decreasing red, then green, then blue */
qsort (cmap, colors, sizeof (COLOR), cmp_cmap);
}
/* Fast square macro, uses local variable to avoid mulitple eval of X */
# define SQR(X) (sqrtmp = (X), sqrtmp*sqrtmp)
/* Fast distance macro */
# define CDIST(CPTR,CR,CG,CB) \
(sumtmp = SQR (((int) ((CPTR)->rd)) - CR), \
sumtmp += SQR (((int) ((CPTR)->gr)) - CG), \
sumtmp += SQR (((int) ((CPTR)->bl)) - CB), \
sumtmp)
# define restrict(X) ((X) < 0 ? 0 : (X) > 255 ? 255 : (X))
nearest (rd, gr, bl, cmap, colors)
int rd, gr, bl;
COLOR *cmap;
int colors;
{ register int clr, sqrtmp, sumtmp;
register COLOR *a, *b, *c, *best, *ctail;
int cindx, bestd, dist;
rd = restrict (rd);
gr = restrict (gr);
bl = restrict (bl);
/* Find array index in cache */
cindx = CLRINDEX8 (rd, gr, bl);
/* Check cache for color value */
if ((clr = cache[cindx]) >= 0)
{ return (clr); }
/*
* Search through colormap for nearest neighbor:
* 1) Find nearest red value by binary search
* 2) Search up and down, keeping best point
* 3) Terminate search when red difference is greater than best pt
*/
/* Binary search for nearest red value */
ctail = &cmap[colors];
for (a=cmap, b= ctail-1; a<b; )
{ c = a + (b-a)/2; /* Find midpoint */
if (debug && verbose)
{ fprintf (stderr, "Searching for %d, a=%d (%d), b=%d (%d), c=%d (%d)\n",
rd, a-cmap, a->rd, b-cmap, b->rd, c-cmap, c->rd);
}
if (c->rd == rd)
{ break; }
else if (c->rd < rd)
{ a = ++c; }
else
{ b = c; }
}
/*
* c now points to closest red value, search up and down for closer
* Euclidean distance, and stop search when the red distance alone
* exceeds the best found.
*/
/* Set best and bestd to best red value */
best = c;
bestd = CDIST (c, rd, gr, bl);
if (debug && verbose)
fprintf (stderr, "Found c=%d (%d) dist = %d\n", c-cmap, c->rd, bestd);
/* a and b are search pointers up and down */
a = c-1;
b = c+1;
while (bestd > 0 && (a >= cmap || b < ctail))
{
if (debug && verbose)
{ fprintf (stderr, " search: bestd %d, best %d|%d, a %d, b %d\n",
bestd, best-cmap, best->indx, a-cmap, b-cmap);
}
if (a >= cmap)
{ if ((dist = CDIST (a, rd, gr, bl)) < bestd)
{ best = a; bestd = dist; }
if (SQR ((int) a->rd - rd) >= bestd) a = cmap-1;
else a--;
}
if (b < ctail)
{ if ((dist = CDIST (b, rd, gr, bl)) < bestd)
{ best = b; bestd = dist; }
if (SQR ((int) b->rd - rd) >= bestd) b = ctail;
else b++;
}
}
if (best < cmap || best >= ctail)
{ perror ("returning invalid color\n");
abort ();
}
clr = (best - cmap);
if (debug && verbose)
{ fprintf (stderr, "Nearest(%3d,%3d,%3d) => <%3d,%3d,%3d> [%d]\n",
rd, gr, bl, best->rd, best->gr, best->bl, best->indx);
}
return ((cache[cindx] = clr));
}
/****************************************************************
* sort colormap by increasing red ( then green and blue )
****************************************************************/
cmp_cmap (a, b)
register COLOR *a, *b;
{ register int r;
/* Put unusable colors at end of list */
if (b->flags & UNUSABLE) return (-1);
/* Else sort by Red, green, blue */
if (r = (a->rd - b->rd)) { return (r); }
if (r = (a->gr - b->gr)) { return (r); }
if (r = (a->bl - b->bl)) { return (r); }
return (0);
}
/****************************************************************
* sort colormap by increasing intensity (Use NTSC weights)
****************************************************************/
# define RW 299
# define GW 587
# define BW 114
cmp_int (a, b)
register COLOR *a, *b;
{ register int ai, bi;
ai = RW * a->rd + GW * a->gr + BW * a->bl;
bi = RW * b->rd + GW * b->gr + BW * b->bl;
return (ai - bi);
}
/****************************************************************
* sort colormap by hue (approx), then intensity
****************************************************************/
find_hue (rd, gr, bl)
int rd, gr, bl;
{ double nrd, ngr, nbl;
double erd, egr, ebl;
double min, max, hv, floor();
int sum, smallest, result, buckets = hue;
double sat;
double sthresh0 = 0.02;
double sthresh1 = 0.17;
/* Greys get hue zero */
if (rd == gr && gr == bl) return (0);
sum = rd + gr + bl;
nrd = (double) rd / sum;
ngr = (double) gr / sum;
nbl = (double) bl / sum;
if (rd < gr && rd <= bl)
{ smallest = RD; min = nrd; max = (gr > bl) ? ngr : nbl; }
else if (gr < bl && gr <= rd)
{ smallest = GR; min = ngr; max = (rd > bl) ? nrd : nbl; }
else
{ smallest = BL; min = nbl; max = (rd > gr) ? nrd : ngr; }
sat = max - min;
/* Check for close to gray (at most 5% difference) */
if (sat < sthresh0) return (0);
/* Subtract out the smallest value (remove that much white) */
erd = nrd - min;
egr = ngr - min;
ebl = nbl - min;
/* Scalar: 0=red, 0.16=yel, 0.33=grn, 0.5=cyn, 0.66=blu, 0.83=mag */
switch (smallest)
{ case BL: hv = (egr / (erd + egr)) / 3.0; break;
case RD: hv = (ebl / (egr + ebl) + 1.0) / 3.0; break;
case GR: hv = (erd / (ebl + erd) + 2.0) / 3.0; break;
}
/* With more than 10 buckets, use 2 levels of saturation */
if (hue > 20)
{ buckets = hue / 2;
result = (int) floor (hv * buckets + 0.5);
result = (result % buckets) + 1;
if (sat >= sthresh1) result += buckets;
}
/* Just use 1 level of saturation (sthresh0 for grey) */
else
{ result = (int) floor (hv * hue + 0.5);
result = (result % hue) + 1;
}
return (result);
}
cmp_hue (a, b)
register COLOR *a, *b;
{ register int ai, bi;
register int h1, h2;
h1 = find_hue (a->rd, a->gr, a->bl);
h2 = find_hue (b->rd, b->gr, b->bl);
if (h1 != h2) return (h1 - h2);
ai = RW * a->rd + GW * a->gr + BW * a->bl;
bi = RW * b->rd + GW * b->gr + BW * b->bl;
return (ai - bi);
}